﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Text.RegularExpressions;
using System.Data.OleDb;

namespace SolutionGenerator
{
    public partial class WizardGeneratingCode : UserControlParent
    {
        public WizardGeneratingCode(SolutionConfiguration configuration) : base(configuration)
        {
            InitializeComponent();
        }
        public override void LoadWizard()
        {//prepraring progress bar
            SolutionGeneratorGenerateCodeProgressBar.Minimum = 0;
            SolutionGeneratorGenerateCodeProgressBar.Maximum = GetSolutionConfiguration.GetTableInfo.Count();
            SolutionGeneratorGenerateCodeOutput.MaxLength = int.MaxValue;
        }
        public override string WizardTitle
        {
            get
            {
                return TextMessage.WizardTitle_GeneratingCode;
            }
        }
        private void SolutionGeneratorGenerateCodeProcessButton_Click(object sender, EventArgs e)
        {
            if (SolutionGeneratorGenerateCodeProcessButton.Text == ParameterValue.ButtonText_Continue)
            {
                SolutionGeneratorGenerateCodeProcessButton.Text = ParameterValue.ButtonText_Cancel;
                SolutionGeneratorGenerateCodeBGWorker.RunWorkerAsync();
                WriteToOuput(string.Format(TextMessage.WizardInfo_ProcessStarted, DateTime.Now));
            }
            else
            {
                SolutionGeneratorGenerateCodeBGWorker.CancelAsync();
                SolutionGeneratorGenerateCodeProcessButton.Text = ParameterValue.ButtonText_Continue;
            }
        }
        private void SolutionGeneratorGenerateCodeBGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                SolutionGeneratorGenerateCodeLabel.Text = TextMessage.WizardInfo_GenerateEnd;
                WriteToOuput(TextMessage.WizardInfo_GenerateEnd);
                SolutionGeneratorGenerateCodeProgressBar.Value = SolutionGeneratorGenerateCodeProgressBar.Maximum;
                SolutionGeneratorGenerateCodeProcessButton.Text = ParameterValue.ButtonText_Continue;
            }
            else
                throw e.Error;
        }
        private void SolutionGeneratorGenerateCodeBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            string message = string.Format(TextMessage.WizardInfo_GenerateInfo, GetSolutionConfiguration.GetTableInfo[e.ProgressPercentage].TableName);
            SolutionGeneratorGenerateCodeLabel.Text = message;
            WriteToOuput(message);
            SolutionGeneratorGenerateCodeProgressBar.Value = e.ProgressPercentage;
        }
        private void SolutionGeneratorGenerateCodeBGWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            List<SolutionConfigurationTableInfo> tables =
                GetSolutionConfiguration.GetTableInfo.Where(_ => _.IsProcess).ToList();
            for (int i = 0; i < tables.Count; i++)
            {
                if (SolutionGeneratorGenerateCodeBGWorker.CancellationPending)
                    return;
                SolutionGeneratorGenerateCodeBGWorker.ReportProgress(i);
                SolutionConfigurationTableInfo tableToProcess = tables[i];
                for (int j = 0; j < GetSolutionConfiguration.GetFolderInfo.Count; j++)
                {
                    SolutionConfigurationFolderInfo confFile = GetSolutionConfiguration.GetFolderInfo[j];
                    string resultedFile = ProcessByTemplate(tableToProcess, confFile);
                    WriteToFile(confFile.FolderLocation, confFile.NameFormat, tableToProcess.TableName, resultedFile);
                }
            }
        }
        private void WriteToFile(string folderLocation, string nameFormat, string tableName, string resultedFile)
        {
            FileInfo file = new FileInfo(string.Format("{0}\\{1}", folderLocation, string.Format(nameFormat, tableName)));
            string message = string.Format(TextMessage.WizardInfo_SaveToFile, file.FullName);
            WriteToOuput(message);
            if (!file.Directory.Exists)
                file.Directory.Create();
            if (file.Exists)
                file.Delete();
            using (FileStream stream = file.Create())
            {
                UTF8Encoding encode = new UTF8Encoding();
                byte[] byteResult = encode.GetBytes(resultedFile);
                stream.Write(byteResult, 0, byteResult.Length);
            }
        }
        private string LoadTemplate(string templateLocation)
        {
            return File.ReadAllText(templateLocation);
        }
        private string PutSpaceBetween(string target)
        {
            StringBuilder result = new StringBuilder(target);
            Regex regCapital = new Regex(ParameterValue.RegEx_FindCapitalChar);
            MatchCollection matchFound = regCapital.Matches(target);
            for (int i = matchFound.Count - 1; i >= 0; i--)
            {
                Match catched = matchFound[i];
                if (catched.Index > 0)
                {
                    result.Remove(catched.Index, catched.Length);
                    result.Insert(catched.Index, string.Format(" {0}", catched.Value));
                }
            }
            return result.ToString();
        }
        /// <summary>
        /// process fk child columns
        /// </summary>
        /// <param name="template"></param>
        /// <param name="tableInfo"></param>
        /// <param name="dataTemplate"></param>
        /// <returns></returns>
        /// <remarks>
        /// Operation
        /// 1. Find ForeignKeyChilds
        /// 2. Find ForeignKeyChildColumns
        /// </remarks>
        private string ProcessFKChildColumns(string template, SolutionConfigurationTableInfo tableInfo,
            List<SolutionConfigurationDataTypeTemplate> dataTemplate)
        {
            StringBuilder result = new StringBuilder(template);
            Regex regFKChild = new Regex(ParameterValue.RegEx_FindForeignKeyChildPlace, RegexOptions.IgnoreCase | RegexOptions.Singleline);
            MatchCollection matchChild = regFKChild.Matches(template);
            for (int j = matchChild.Count - 1; j >= 0; j--)
            {
                Group catchChild = matchChild[j].Groups[1];
                StringBuilder fkBuilt = new StringBuilder();
                if (tableInfo.IsFKChildProcess)
                {
                    fkBuilt.Append(catchChild.Value);
                    Regex regFKChildColumn = new Regex(ParameterValue.RegEx_FindForeignKeyChildColumns, RegexOptions.IgnoreCase | RegexOptions.Singleline);
                    MatchCollection matchFound = regFKChildColumn.Matches(catchChild.Value);
                    foreach (SolutionConfigurationColumnInfo fkColumn in tableInfo.GetColumnInfo.
                        Where(_ => _.FKList != null && _.FKList.Count(p => p.FKType == ForeignKeyType.Child) > 0))
                    {
                        foreach (SolutionConfigurationForeignKeyInfo fkInfo in fkColumn.FKList)
                        {
                            WriteToOuput(string.Format(TextMessage.WizardInfo_ProcessFKChilds, fkInfo.FKTable));
                            SolutionConfigurationTableInfo fkChildTable = FindTableByName(fkInfo.FKTable);
                            for (int i = matchFound.Count - 1; i >= 0; i--)
                            {
                                StringBuilder columnResult = new StringBuilder();
                                Group catched = matchFound[i].Groups[1];
                                if (fkInfo.FKType == ForeignKeyType.Child)
                                {
                                    StringBuilder resultMatch = new StringBuilder(matchFound[i].Value);
                                    resultMatch.Replace(ParameterValue.ReplaceText_FKChildsColumnBegin, ParameterValue.ReplaceText_ColumnsNameBegin);
                                    resultMatch.Replace(ParameterValue.ReplaceText_FKChildsColumnEnd, ParameterValue.ReplaceText_ColumnsNameEnd);
                                    resultMatch.Replace(ParameterValue.ReplaceText_FKChildColumnName, fkInfo.FKColumn);
                                    string resultTemplate = ProcessTemplate(resultMatch.ToString(), fkChildTable, dataTemplate);
                                    resultMatch.Clear();
                                    resultMatch.Append(resultTemplate);
                                    columnResult.Append(resultMatch.ToString());
                                }
                                fkBuilt.Remove(matchFound[i].Index, matchFound[i].Length).Insert(matchFound[i].Index, columnResult.ToString());
                            }
                            fkBuilt.Replace(ParameterValue.ReplaceText_FKChildTableName, fkChildTable.TableName);
                            fkBuilt.Replace(ParameterValue.ReplaceText_FKChildTableNameSmallCharInFront, fkChildTable.TableNameSmallCharInfront);
                            fkBuilt.Replace(ParameterValue.ReplaceText_FKChildTableNamePutSpaceBetweenCaps, PutSpaceBetween(fkChildTable.TableName));
                        }
                    }
                }
                result.Remove(matchChild[j].Index, matchChild[j].Length).Insert(matchChild[j].Index, fkBuilt.ToString());
            }
            return result.ToString();
        }
        private SolutionConfigurationTableInfo FindTableByName(string name)
        {
            return GetSolutionConfiguration.GetTableInfo.Single(_ => _.TableName == name);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="template"></param>
        /// <param name="tableInfo"></param>
        /// <returns></returns>
        /// <remarks>
        /// Operation
        /// 1. Replace table name
        /// 2. Find using regex TableColumns
        /// 3. Process each column
        /// </remarks>
        private string ProcessTemplate(string template, SolutionConfigurationTableInfo tableInfo, 
            List<SolutionConfigurationDataTypeTemplate> dataTemplate)
        {
            StringBuilder result = new StringBuilder(template);
            Regex regColumns = new Regex(ParameterValue.RegEx_FindTableColumns, RegexOptions.IgnoreCase | RegexOptions.Singleline);
            MatchCollection matchFound = regColumns.Matches(template);
            for (int i = matchFound.Count - 1; i >= 0; i--)
            {
                Group catched = matchFound[i].Groups[1];
                WriteToOuput(string.Format(TextMessage.WizardInfo_ProcessColumnLoop, catched.Value));
                StringBuilder columnResult = new StringBuilder();
                for (int j = 0; j < tableInfo.GetColumnInfo.Count; j++)
                {
                    SolutionConfigurationColumnInfo column = tableInfo.GetColumnInfo[j];
                    Match foundExclude = FindExclude(catched.Value);
                    bool isExclude = false;
                    if (foundExclude.Success)
                        isExclude = ExcludeThisColumn(foundExclude.Groups[1].Value, column.ColumnName);
                    if (!isExclude)
                    {
                        WriteToOuput(string.Format(TextMessage.WizardInfo_ProcessColumn, column.ColumnName));
                        StringBuilder columnBuiltUp = new StringBuilder(catched.Value);
                        if (foundExclude.Success)
                            columnBuiltUp.Replace(foundExclude.Value, string.Empty);
                        Match seperate = FindSeperate(catched.Value);
                        if (seperate.Success)
                        {
                            columnBuiltUp.Replace(seperate.Value, string.Empty);
                            if (columnResult.Length > 0)
                            {
                                if (j > 0)
                                {
                                    string seperateChar = GetSeperateChar(seperate.Value);
                                    columnResult.Append(seperateChar);
                                }
                            }
                        }
                        SolutionConfigurationDataTypeTemplate dataTypeTemplate = FindDataTypeInTemplate(column.ColumnDataType, dataTemplate);
                        //process fk parent 1st
                        foreach (Match foundFKParent in FindFKParents(columnBuiltUp.ToString()))
                        {
                            if (column.FKList != null)
                            {
                                SolutionConfigurationForeignKeyInfo FKParent = column.FKList.FirstOrDefault(_ => _.FKType == ForeignKeyType.Parent);
                                if (FKParent != null)
                                {
                                    SolutionConfigurationTableInfo parent = FindTableByName(FKParent.FKTable);
                                    SolutionConfigurationColumnInfo findColumn = parent.GetColumnInfo.Single(_ => _.ColumnName == FKParent.FKColumn);
                                    columnBuiltUp.Replace(foundFKParent.Value, ReplaceFKWithDataTypeTemplateInIndex(foundFKParent.Value, dataTypeTemplate.TemplateReplace));
                                    columnBuiltUp.Replace(ParameterValue.ReplaceText_FKParentColumnName, findColumn.ColumnName);
                                    columnBuiltUp.Replace(ParameterValue.ReplaceText_FKParentColumnPutSpaceBetweenCaps, PutSpaceBetween(findColumn.ColumnName));
                                    columnBuiltUp.Replace(ParameterValue.ReplaceText_FKParentColumnSmallCharInFront, findColumn.ColumnNameSmallCharInfront);
                                    columnBuiltUp.Replace(ParameterValue.ReplaceText_FKParentTableName, parent.TableName);
                                    columnBuiltUp.Replace(ParameterValue.ReplaceText_FKParentTableNamePutSpaceBetweenCaps, PutSpaceBetween(parent.TableName));
                                    columnBuiltUp.Replace(ParameterValue.ReplaceText_FKParentTableNameSmallCharInFront, parent.TableNameSmallCharInfront);
                                    continue;
                                }
                            }
                            columnBuiltUp.Replace(foundFKParent.Value, ReplaceWithColumnTypeAt(foundFKParent.Value, dataTypeTemplate.TemplateReplace));
                        }
                        foreach (Match foundNullable in FindNullable(columnBuiltUp.ToString()))
                        {
                            if (column.IsNullAble)
                                columnBuiltUp.Replace(foundNullable.Value, FindDataTypeTemplateInIndexForNullable(foundNullable.Value, dataTypeTemplate.TemplateReplace));
                            else
                                columnBuiltUp.Replace(foundNullable.Value, FindDataTypeTemplateInIndex(foundNullable.Value, dataTypeTemplate.TemplateReplace));
                        }
                        foreach (Match foundColumnType in FindColumnTypes(columnBuiltUp.ToString()))
                            columnBuiltUp.Replace(foundColumnType.Value, FindDataTypeTemplateInIndex(foundColumnType.Value, dataTypeTemplate.TemplateReplace));
                        if (dataTypeTemplate.DefaultValueMap != null)
                        {
                            if (dataTypeTemplate.DefaultValueMap.ContainsKey(column.DefaultValue))
                                columnBuiltUp.Replace(ParameterValue.ReplaceText_DefaultValue, dataTypeTemplate.DefaultValueMap[column.DefaultValue]);
                            else
                                columnBuiltUp.Replace(ParameterValue.ReplaceText_DefaultValue, string.Empty);
                        }
                        foreach (Match foundIsRequired in FindIsRequireds(columnBuiltUp.ToString()))
                        {
                            if (!column.IsNullAble)
                                columnBuiltUp.Replace(foundIsRequired.Value,
                                    FindDataTypeIsRequiredInTemplate(foundIsRequired.Value, dataTypeTemplate.TemplateIsRequired));
                            else
                                columnBuiltUp.Remove(foundIsRequired.Index, foundIsRequired.Length);
                        }
                        columnBuiltUp.Replace(ParameterValue.ReplaceText_ColumnLength, column.ColumnLength.ToString());
                        columnBuiltUp.Replace(ParameterValue.ReplaceText_ColumnName, column.ColumnName);
                        columnBuiltUp.Replace(ParameterValue.ReplaceText_ColumnNameSmallCharInFront, column.ColumnNameSmallCharInfront);
                        columnBuiltUp.Replace(ParameterValue.ReplaceText_ColumnNamePutSpaceBetweenCaps, PutSpaceBetween(column.ColumnName));
                        columnResult.Append(columnBuiltUp.ToString());
                    }
                    else
                    {
                        WriteToOuput(string.Format(TextMessage.WizardInfo_ExcludeColumn, column.ColumnName));
                    }
                }
                result.Remove(matchFound[i].Index, matchFound[i].Length).Insert(matchFound[i].Index, columnResult.ToString());
            }
            result.Replace(ParameterValue.ReplaceText_TableName, tableInfo.TableName);
            result.Replace(ParameterValue.ReplaceText_TableNameSmallCharInFront, tableInfo.TableNameSmallCharInfront);
            result.Replace(ParameterValue.ReplaceText_TableNamePutSpaceBetweenCaps, PutSpaceBetween(tableInfo.TableName));
            return result.ToString();
        }
        private Match FindExclude(string catched)
        {
            Regex regExclude = new Regex(ParameterValue.RegEx_FindExcludeColumn, RegexOptions.IgnoreCase | RegexOptions.Singleline);
            return regExclude.Match(catched);
        }
        private Match FindSeperate(string catched)
        {
            Regex seperateEx = new Regex(ParameterValue.RegEx_FindSeperateChar);
            return seperateEx.Match(catched);
        }
        private MatchCollection FindFKParents(string catched)
        {
            Regex findFKParent = new Regex(ParameterValue.RegEx_FindForeignKeyParent);
            return findFKParent.Matches(catched);
        }
        private MatchCollection FindNullable(string columnBuiltUp)
        {
            Regex findNullableType = new Regex(ParameterValue.RegEx_Nullable);
            return findNullableType.Matches(columnBuiltUp);
        }
        private MatchCollection FindColumnTypes(string columnBuiltUp)
        {
            Regex findColumnType = new Regex(ParameterValue.RegEx_FindColumnType);
            return findColumnType.Matches(columnBuiltUp);
        }
        private MatchCollection FindIsRequireds(string columnBuiltUp)
        {
            Regex findIsRequired = new Regex(ParameterValue.ReplaceText_IsRequired);
            return findIsRequired.Matches(columnBuiltUp);
        }
        private string ReplaceFKWithDataTypeTemplateInIndex(string foundFKParent, Dictionary<int, string> templateReplaceList)
        {
            Regex number = new Regex(ParameterValue.RegEx_FindNumber);
            Match found = number.Match(foundFKParent);
            if (found.Success)
            {
                int index = int.Parse(found.Value);
                return FindDataTypeTemplateInIndex(BuiltDummyColumnType(index), templateReplaceList);
            }
            throw new InvalidOperationException(string.Format(TextMessage.Error_DataTypeNotFound, foundFKParent));
        }
        private string ReplaceWithColumnTypeAt(string foundFKParent, Dictionary<int, string> templateReplaceList)
        {
            Regex number = new Regex(ParameterValue.RegEx_FindNumber);
            Match found = number.Match(foundFKParent);
            if (found.Success)
            {
                found = found.NextMatch();
                if (found.Success)
                {
                    int index;
                    if (found.NextMatch().Success)
                    {
                        int nullableIndex = int.Parse(found.NextMatch().Value);
                        index = int.Parse(found.Value);
                        return string.Format(ParameterValue.Text_DummyNullable, nullableIndex, index);
                    }
                    else
                    {
                        index = int.Parse(found.Value);
                        return FindDataTypeTemplateInIndex(BuiltDummyColumnType(index), templateReplaceList);
                    }
                }
                else
                    return "";
            }
            throw new InvalidOperationException(string.Format(TextMessage.Error_DataTypeNotFound, foundFKParent));
        }
        private string BuiltDummyColumnType(int index)
        {
            return string.Format(ParameterValue.Text_DummyColumnType, index);
        }
        private string BuiltDummyNullable(int nullableIndex, int defaultIndex)
        {
            return string.Format(ParameterValue.Text_DummyNullable, nullableIndex, defaultIndex);
        }
        private string FindDataTypeTemplateInIndexForNullable(string nullableColumnType, Dictionary<int, string> dataTypeTemplate)
        {
            Regex findIndex = new Regex(ParameterValue.RegEx_Nullable);
            Match found = findIndex.Match(nullableColumnType);
            if (found.Success)
            {
                int index = int.Parse(found.Groups[1].Value);
                return dataTypeTemplate[index];
            }
            throw new InvalidOperationException(string.Format(TextMessage.Error_DataTypeNotFound, nullableColumnType));
        }
        private string FindDataTypeTemplateInIndex(string foundColumnType, Dictionary<int, string> dataTypeTemplate)
        {
            Regex findIndex = new Regex(ParameterValue.ReplaceText_ColumnType);
            Match found = findIndex.Match(foundColumnType);
            if (found.Success)
            {
                int index = int.Parse(found.Groups[2].Value);
                return dataTypeTemplate[index];
            }
            throw new InvalidOperationException(string.Format(TextMessage.Error_DataTypeNotFound, foundColumnType));
        }
        private string GetSeperateChar(string seperateFound)
        {
            Regex findChar = new Regex(ParameterValue.RegEx_FindSeperateChar);
            Match found = findChar.Match(seperateFound);
            if (found.Success)
                return found.Groups[1].Value;
            throw new InvalidOperationException(string.Format(TextMessage.Error_SeperateCharNotFound, seperateFound));
        }
        private string FindDataTypeIsRequiredInTemplate(string found, Dictionary<int, string> templateIsRequired)
        {
            Regex findIndex = new Regex(ParameterValue.RegEx_FindIsRequiredIndex);
            Match capture = findIndex.Match(found);
            if (capture.Success)
            {
                int index = int.Parse(capture.Groups[1].Value);
                return templateIsRequired[index];
            }
            throw new InvalidOperationException(string.Format(TextMessage.Error_DataTypeNotFound, found));
        }
        private bool ExcludeThisColumn(string foundMatch, string columnName)
        {
            string[] columns = foundMatch.Split(ParameterValue.DefaultText_Seperate.ToCharArray());
            return columns.Count(_ => _.Trim() == columnName) > 0;
        }
        private SolutionConfigurationDataTypeTemplate FindDataTypeInTemplate(OleDbType oleDbType, List<SolutionConfigurationDataTypeTemplate> dataTemplate)
        {
            switch ((OleDbType)oleDbType)
            {
                //case (OleDbType)132://udt
                //case (OleDbType)141://xml
                case (OleDbType)145://time
                    oleDbType = OleDbType.DBTime;
                    break;
                //case (OleDbType)146://Datetimeoffset
            }
            SolutionConfigurationDataTypeTemplate result = dataTemplate.FirstOrDefault(_ => _.DataType == oleDbType);
            if (result != null)
                return result;
            else
                throw new InvalidOperationException(string.Format(TextMessage.Error_DataTypeNotFound, oleDbType));
        }
        private List<SolutionConfigurationDataTypeTemplate> LoadDataType(string dataTypeTemplateFile)
        {
            using (SerializeProcessing<List<SolutionConfigurationDataTypeTemplate>> serial =
                new SerializeProcessing<List<SolutionConfigurationDataTypeTemplate>>())
            {
                string contentFile = File.ReadAllText(dataTypeTemplateFile);
                return serial.Deserialize(contentFile);
            }
        }
        private string ProcessByTemplate(SolutionConfigurationTableInfo tableInfo, SolutionConfigurationFolderInfo folderInfo)
        {
            WriteToOuput(string.Format(TextMessage.WizardInfo_ProcessTemplate, folderInfo.DataTypeTemplate));
            string result = LoadTemplate(folderInfo.UseThisTemplate);
            List<SolutionConfigurationDataTypeTemplate> dataTypeTemplate = LoadDataType(folderInfo.DataTypeTemplate);
            result = ProcessFKChildColumns(result, tableInfo, dataTypeTemplate);
            result = ProcessTemplate(result, tableInfo, dataTypeTemplate);
            return result;
        }
        delegate void SetTextCallback(string text);
        private void WriteToOuput(string message)
        {
            if (SolutionGeneratorGenerateCodeOutput.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(WriteToOuput);
                this.Invoke(d, new object[] { message });
            }
            else
            {
                SolutionGeneratorGenerateCodeOutput.AppendText(string.Format("{0:HH:mm:ss.fff} {1}\r\n",
                    DateTime.Now, message));
            }
        }
    }
}